package io.adrainty.bolee.framework.commons.utils;

import cn.hutool.core.io.IoUtil;
import io.adrainty.bolee.framework.commons.exception.CoreException;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author AdRainty
 * @version V1.0.0
 * @since 2025/7/27 下午11:53
 */

@Slf4j
@SuppressWarnings("unused")
public class RegionUtil {

    private RegionUtil() {
    }

    private static final String DB_PATH = "ip2region.xdb";

    private static final String UNKNOWN = "unknown";

    private static final Searcher SEARCHER;

    static final RegionData DEFAULT_REGION = RegionData.builder()
            .country(UNKNOWN)
            .region(UNKNOWN)
            .province(UNKNOWN)
            .city(UNKNOWN)
            .isp(UNKNOWN)
            .build();

    static {
        try (InputStream is = RegionUtil.class.getClassLoader().getResourceAsStream("ip2region.xdb")) {
            byte[] byteData = IoUtil.readBytes(is);
            SEARCHER = Searcher.newWithBuffer(byteData);
        } catch (IOException e) {
            log.error("load ip2region.xdb file error");
            throw new CoreException(e);
        }
    }

    /**
     * Retrieves the geographic region corresponding to the given IP address.
     *
     * @param ip The IP address in long format.
     * @return The geographic region associated with the IP address.
     * @throws RuntimeException if an exception occurs during the region lookup process.
     */
    public static RegionData getRegion(long ip) {
        try {
            return convert(SEARCHER.search(ip));
        } catch (Exception e) {
            return handleError(e);
        }
    }

    /**
     * Retrieves the geographic region corresponding to the given IP address.
     *
     * @param ip The IP address in String format.
     * @return The geographic region associated with the IP address.
     * @throws RuntimeException if an exception occurs during the region lookup process.
     */
    public static RegionData getRegion(String ip) {
        try {
            return convert(SEARCHER.search(ip));
        } catch (Exception e) {
            return handleError(e);
        }
    }

    private static RegionData handleError(Throwable e) {
        log.error("Convert ip to RegionData error, cause by [{}], will return default region.", e.getMessage());
        return DEFAULT_REGION;
    }

    /**
     * Converts a region string into a Region object.
     *
     * @param regionStr The region string to be converted.
     * @return A Region object representing the converted region string, or null if the input is invalid or empty.
     */
    private static RegionData convert(String regionStr) {
        if (StringUtils.isBlank(regionStr) || UNKNOWN.equalsIgnoreCase(regionStr)) {
            return DEFAULT_REGION;
        }
        String[] regionSplit = regionStr.split("\\|");
        if (regionSplit.length != 5) {
            return DEFAULT_REGION;
        }
        return RegionData.builder()
                .country(getOrDefaultStr(regionSplit[0]))
                .region(getOrDefaultStr(regionSplit[1]))
                .province(getOrDefaultStr(regionSplit[2]))
                .city(getOrDefaultStr(regionSplit[3]))
                .isp(getOrDefaultStr(regionSplit[4]))
                .build();
    }

    private static String getOrDefaultStr(String origin) {
        if (StringUtils.isBlank(origin) || origin.equals("0")) return UNKNOWN;
        return origin;
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Schema(description = "区域信息")
    public static class RegionData {

        static final String REGION_SPLIT = "|";

        @Schema(description = "国家")
        private String country;

        @Schema(description = "区域")
        private String region;

        @Schema(description = "省份")
        private String province;

        @Schema(description = "城市")
        private String city;

        @Schema(description = "运营商")
        private String isp;

        @Override
        public String toString() {
            return country + REGION_SPLIT + region + REGION_SPLIT + province + REGION_SPLIT + city + REGION_SPLIT + isp;
        }
    }

}
